package cn.st4rlight.util.excel;

import java.io.InputStream;
import java.util.List;
import java.util.ListIterator;
import java.util.Objects;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.web.multipart.MultipartFile;

import com.alibaba.excel.EasyExcelFactory;
import com.alibaba.excel.ExcelReader;
import com.alibaba.excel.event.SyncReadListener;
import com.alibaba.excel.read.metadata.ReadSheet;
import com.google.common.collect.Lists;

import cn.st4rlight.util.collection.StreamUtil;
import cn.st4rlight.util.exception.ServiceException;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;

/**
 * @author st4rlight <st4rlight@163.com>
 * Created on 2024-04-15
 */
@Slf4j
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class ReadExcelUtil {

    /**
     * 从MultipartFile中读取excel表格
     * NOTE: 该方法会清除末尾的空白行
     */
    public static <T> List<T> readFromMultiPart(MultipartFile multipartFile, Class<T> clz) {
        List<T> dtoList;
        try {
            dtoList = readSingleSheetExcel(multipartFile.getInputStream(), clz);
            dtoList = StreamUtil.filter(dtoList, Objects::nonNull);
        } catch (Exception ex) {
            log.error("readFromMultiPart 读取excel文件异常", ex);
            throw ServiceException.of(ex.getMessage());
        }

        // 清除末尾的空白行
        ListIterator<T> reverseIterator = dtoList.listIterator(dtoList.size());
        while (reverseIterator.hasPrevious()) {
            if (Objects.isNull(reverseIterator.previous())) {
                reverseIterator.remove();
            } else {
                break;
            }
        }
        return dtoList;
    }

    /**
     * 读取单个sheet的excel表格
     *
     * @param dtoClz 目标对象类型
     * @return 读取结果列表
     */
    @SuppressWarnings("unchecked")
    public static <T> List<T> readSingleSheetExcel(InputStream inputStream, Class<T> dtoClz) {
        if (!ObjectUtils.allNotNull(inputStream, dtoClz)) {
            throw ServiceException.of("用于构建excel的上下文参数列表不能为空");
        }

        try {
            SyncReadListener syncReadListener = new SyncReadListener();
            EasyExcelFactory.read(inputStream, dtoClz, syncReadListener).sheet().doRead();
            return (List<T>) syncReadListener.getList();
        } catch (Exception ex) {
            log.error("读取单个sheet的excel文件异常", ex);
            throw ServiceException.of(ex.getMessage());
        } finally {
            IOUtils.closeQuietly(inputStream);
        }
    }

    /**
     * 读取多个sheet的excel表格
     * 注意：传入的dtoClzList要与sheet的顺序保持一致
     *
     * @param dtoClzList 目标对象类型列表
     * @return 读取结果列表
     */
    public static List<List<Object>> readMultiSheetExcel(InputStream inputStream, List<Class<?>> dtoClzList) {
        if (Objects.isNull(inputStream) || CollectionUtils.isEmpty(dtoClzList)) {
            throw ServiceException.of("用于构建excel的上下文参数列表不能为空");
        }

        try(
            ExcelReader excelReader = EasyExcelFactory.read(inputStream).build()
        ) {
            List<ReadSheet> readSheetList = Lists.newArrayList();
            List<SyncReadListener> listenerList = Lists.newArrayList();

            // 一次性读取所有sheet
            for (int i = 0; i < dtoClzList.size(); i++) {
                log.info("readMultiSheetExcel {} / {}", i + 1, dtoClzList.size());
                SyncReadListener syncReadListener = new SyncReadListener();
                ReadSheet readSheet = EasyExcelFactory.readSheet(i)
                        .head(dtoClzList.get(i))
                        .registerReadListener(syncReadListener)
                        .build();
                readSheetList.add(readSheet);
                listenerList.add(syncReadListener);
            }
            excelReader.read(readSheetList);

            // 获取读取结果
            List<List<Object>> resultList = Lists.newArrayList();
            listenerList.forEach(listener -> resultList.add(listener.getList()));
            return resultList;
        } catch (Exception ex) {
            log.error("读取多个sheet的excel文件异常", ex);
            throw ServiceException.of(ex.getMessage());
        } finally {
            IOUtils.closeQuietly(inputStream);
        }
    }
}
